using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using JigLibX.Physics;
using JigLibX.Collision;
using JigLibX.Geometry;
using JigLibX.Math;
using JigLibX.Utils;
using JigLibX.Vehicles;
using ImportModelFromRhino.PhysicObjects;
using System.Diagnostics;

namespace ImportModelFromRhino
{
    #region ImmovableSkinPredicate
    class ImmovableSkinPredicate : CollisionSkinPredicate1
    {
        public override bool ConsiderSkin(CollisionSkin skin0)
        {
            if (skin0.Owner != null && !skin0.Owner.Immovable)
                return true;
            else
                return false;
        }
    }
    #endregion

    public class Game1 : Microsoft.Xna.Framework.Game
    {
        MouseState mouseState, oldMouseState;
        FrameRateCounter frameRateCounter;
        CasettaDelMicioObject casettaDelMicio;
        DebugDrawer debugDrawer;
        GraphicsDeviceManager graphics;
        CameraSplitScreen camera;
        public ModelManagerSplitScreen modelmanager;
        Viewport defaultViewport, leftViewport, rightViewport;
        Viewport[] viewports;
        Matrix halfprojectionMatrix;
        MenuHelper menuHelper;

        PhysicsSystem physicSystem;

        ConstraintWorldPoint objectController = new ConstraintWorldPoint();
        ConstraintVelocity damperController = new ConstraintVelocity();

        Model boxModel;
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);

            Content.RootDirectory = "Content";

            physicSystem = new PhysicsSystem();
            physicSystem.CollisionSystem = new CollisionSystemSAP();

            physicSystem.EnableFreezing = true;
            physicSystem.SolverType = PhysicsSystem.Solver.Normal;
            physicSystem.CollisionSystem.UseSweepTests = true;

            physicSystem.NumCollisionIterations = 10;
            physicSystem.NumContactIterations = 10;
            physicSystem.NumPenetrationRelaxtionTimesteps = 15;

            frameRateCounter = new FrameRateCounter(this, this.physicSystem);
            Components.Add(frameRateCounter);

            debugDrawer = new DebugDrawer(this);
            debugDrawer.Enabled = false;
            Components.Add(debugDrawer);
            this.IsMouseVisible = true;
        }

        protected override void Initialize()
        {
            oldMouseState = Mouse.GetState();
            defaultViewport = graphics.GraphicsDevice.Viewport;
            leftViewport = defaultViewport;
            rightViewport = defaultViewport;
            leftViewport.Width = leftViewport.Width / 2;
            rightViewport.Width = rightViewport.Width / 2;
            rightViewport.X = leftViewport.Width;
            viewports = new Viewport[]{ leftViewport, rightViewport };
            halfprojectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, (float)leftViewport.Width / leftViewport.Height, 1.0f, 10000f);

            var positionCameras = new Vector3[]{new Vector3(0, 10, 100), 
                                                new Vector3(0, 10,-100)};

            var rotationCameras = new Vector3[]{new Vector3(0, -MathHelper.PiOver4 / 4, 0), 
                                                new Vector3(MathHelper.Pi, -MathHelper.PiOver4 / 4, 0)};

            menuHelper = new MenuHelper(this, defaultViewport);
            Components.Add(menuHelper);

            camera = new CameraSplitScreen(this, positionCameras, rotationCameras, halfprojectionMatrix, rightViewport, leftViewport);
            Components.Add(camera);

            modelmanager = new ModelManagerSplitScreen(this, camera, leftViewport, rightViewport);
            Components.Add(modelmanager);

            
            base.Initialize();
        }

        protected override void LoadContent()
        {
            var casettaDelMicioModel = Content.Load<Model>("Models\\casetta del micio");

            casettaDelMicio = new CasettaDelMicioObject(this, casettaDelMicioModel, new Vector3(0, 0, 0), viewports);
            Components.Add(casettaDelMicio);

            //var gattoModel = Content.Load<Model>("Models/GATTO");
            //gattoObject gatto = new gattoObject(this, gattoModel, Matrix.CreateRotationX(-MathHelper.PiOver2), new Vector3(0, 12.7f, 0), new Vector3(1, 1, 1), viewports);
            //Components.Add(gatto);

            boxModel = Content.Load<Model>("Models\\box");

            createScene1();
        }

        private void createScene1()
        {
            for (int j = 0; j <= 5; j++)
            {
                for (int i = 0; i <= 5; i++)
                {
                    BoxObject obj = new BoxObject(this, boxModel, new Vector3(2f, 2f, 2f), Matrix.Identity, new Vector3(i * 2.1f - 5, 13 + j * 2.1f, 0), viewports);
                    Components.Add(obj);
                }                
            }
        }

        #region Update

        float camPickDistance = 0.0f;
        bool middleButton = false;
        int oldWheel = 0;

        protected override void Update(GameTime gameTime)
        {
            mouseState = Mouse.GetState();

            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
                Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();

            if (Keyboard.GetState().IsKeyDown(Keys.RightControl))
            {
                spanBox();
            }

            if (mouseState.RightButton == ButtonState.Released && oldMouseState.RightButton == ButtonState.Pressed)
            {
                createScene1();
            }

            if (mouseState.LeftButton == ButtonState.Released && oldMouseState.LeftButton == ButtonState.Pressed)
            {
                BoxObject obj = new BoxObject(this, boxModel, new Vector3(2f, 2f, 2f), camera.viewRotation[0], camera.pos[0] + camera.viewRotation[0].Forward * 10, viewports);

                obj.PhysicsBody.Velocity = (camera.viewRotation[0].Forward) * 100.0f;
                Components.Add(obj);
            }

            debugDrawer.Enabled = Keyboard.GetState().IsKeyDown(Keys.C);

            frameRateCounter.mario = Components.Count.ToString() + " Components!";

            float timeStep = (float)gameTime.ElapsedGameTime.Ticks / TimeSpan.TicksPerSecond;
            physicSystem.Integrate(timeStep);

            #region Picking Objects with the mouse
            if (mouseState.LeftButton == ButtonState.Pressed)
            {
                if (middleButton == false)
                {
                    Vector3 ray = RayTo(mouseState.X, mouseState.Y);
                    float frac; CollisionSkin skin;
                    Vector3 pos, normal;

                    ImmovableSkinPredicate pred = new ImmovableSkinPredicate();

                    physicSystem.CollisionSystem.SegmentIntersect(out frac, out skin, out pos, out normal,
                        new Segment(camera.pos[0], ray * 1000.0f), pred);

                    if (skin != null && (skin.Owner != null))
                    {
                        if (!skin.Owner.Immovable)
                        {
                            Vector3 delta = pos - skin.Owner.Position;
                            delta = Vector3.Transform(delta, Matrix.Transpose(skin.Owner.Orientation));

                            camPickDistance = (camera.pos[0] - pos).Length();
                            oldWheel = mouseState.ScrollWheelValue;

                            skin.Owner.SetActive();
                            objectController.Destroy();
                            damperController.Destroy();
                            objectController.Initialise(skin.Owner, delta, pos);
                            damperController.Initialise(skin.Owner, ConstraintVelocity.ReferenceFrame.Body, Vector3.Zero, Vector3.Zero);
                            objectController.EnableConstraint();
                            damperController.EnableConstraint();
                        }
                    }

                    middleButton = true;
                }

                if (objectController.IsConstraintEnabled && (objectController.Body != null))
                {
                    Vector3 delta = objectController.Body.Position - camera.pos[0];
                    Vector3 ray = RayTo(mouseState.X, mouseState.Y); ray.Normalize();
                    float deltaWheel = mouseState.ScrollWheelValue - oldWheel;
                    camPickDistance += deltaWheel * 0.01f;
                    Vector3 result = camera.pos[0] + camPickDistance * ray;
                    oldWheel = mouseState.ScrollWheelValue;
                    objectController.WorldPosition = result;
                    objectController.Body.SetActive();
                }
            }
            else
            {
                objectController.DisableConstraint();
                damperController.DisableConstraint();
                middleButton = false;
            }
            #endregion

            oldMouseState = Mouse.GetState();
            base.Update(gameTime);
        }
        #endregion

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            base.Draw(gameTime);

            //Disegno i modelli
            modelmanager.Draw();

            //Disegno le camere
            camera.Draw();

            //Disegno il menu helper
            menuHelper.draw();
        }

        #region Utility
        public CameraSplitScreen Camera
        {
            get { return camera; }
        }

        public DebugDrawer DebugDrawer
        {
            get { return debugDrawer; }
        }

        private void spanBox()
        {
            var random = new Random();
            Viewport[] viewports = { leftViewport, rightViewport };
            BoxObject obj = new BoxObject(this, boxModel, new Vector3(2, 2, 2), Matrix.Identity, new Vector3(random.Next(-10, 10), 100, random.Next(-10, 10)), viewports);
            this.Components.Add(obj);
        }

        private Vector3 RayTo(int x, int y)
        {
            Vector3 nearSource = new Vector3(x, y, 0);
            Vector3 farSource = new Vector3(x, y, 1);

            Matrix world = Matrix.CreateTranslation(0, 0, 0);

            Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, camera.projection, camera.view[0], world);
            Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, camera.projection, camera.view[0], world);

            Vector3 direction = farPoint - nearPoint;
            //direction.Normalize();
            return direction;
        }
        #endregion

    }
}
